/********************************************************************************
 * Copyright (c) 2011-2017 Red Hat Inc. and/or its affiliates and others
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 1.0 which is available at
 * http://www.eclipse.org/legal/epl-v10.html.
 *
 * SPDX-License-Identifier: EPL-1.0
 ********************************************************************************/
package org.eclipse.ceylon.ide.eclipse.code.correct;

import static org.eclipse.ceylon.ide.eclipse.java2ceylon.Java2CeylonProxies.utilJ2C;

import java.util.Collection;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.ltk.core.refactoring.TextChange;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.text.edits.ReplaceEdit;

import org.eclipse.ceylon.compiler.typechecker.tree.Node;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree.Block;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree.BooleanCondition;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree.ComparisonOp;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree.Condition;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree.EqualOp;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree.EqualityOp;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree.Expression;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree.IfClause;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree.IfExpression;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree.IfStatement;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree.LargeAsOp;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree.LargerOp;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree.NotOp;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree.SmallAsOp;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree.SmallerOp;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree.Statement;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree.Term;
import org.eclipse.ceylon.compiler.typechecker.tree.Visitor;

class InvertIfElseProposal extends CorrectionProposal {
    
    private InvertIfElseProposal(int offset, String desc, 
            TextChange change) {
        super(desc, change, new Region(offset, 0));
    }
    
    static void addInvertIfElseProposal(IDocument doc,
            Collection<ICompletionProposal> proposals, 
            IFile file, Statement statement, Node node ,
            Tree.CompilationUnit cu) {
        addInvertIfElseExpressionProposal(doc, proposals, 
                file, node, cu);
        addInvertIfElseStatementProposal(doc, proposals, 
                file, statement, cu);
    }
    
    static void addInvertIfElseExpressionProposal(IDocument doc,
            Collection<ICompletionProposal> proposals, 
            IFile file, final Node node, 
            Tree.CompilationUnit cu) {
    try {
        IfExpression ifExpr;
        class FindIf extends Visitor {
            IfExpression result;
            @Override
            public void visit(IfExpression that) {
                super.visit(that);
                if (that.getIfClause()!=null && 
                    that.getElseClause()!=null &&
                        that.getStartIndex()<=node.getStartIndex() &&
                        that.getEndIndex()>=node.getEndIndex()) {
                    result = that;
                }
            }
        }
        FindIf fi = new FindIf();
        fi.visit(cu);
        if (fi.result==null) {
            return;
        }
        else {
            ifExpr = fi.result;
        }
        
        IfClause ifClause = ifExpr.getIfClause();
        Expression ifBlock = ifClause.getExpression();
        Expression elseBlock = 
                ifExpr.getElseClause()
                    .getExpression();
        List<Condition> conditions = 
                ifClause.getConditionList()
                    .getConditions();
        if (conditions.size()!=1) {
            return;
        }
        Condition ifCondition = conditions.get(0);
        
        String test = null;
        String term = getTerm(doc, ifCondition);
        if (term.equals("(true)")) {
            test = "false";
        } else if (term.equals("(false)")) {
            test = "true";
        } else if (ifCondition instanceof BooleanCondition) {
            BooleanCondition boolCond = 
                    (BooleanCondition) ifCondition;
            Term bt = boolCond.getExpression().getTerm();
            if (bt instanceof NotOp) {
                NotOp no = (NotOp) bt;
                String t = getTerm(doc, no.getTerm());
                test = removeEnclosingParenthesis(t);
            } else if (bt instanceof EqualityOp) {
                EqualityOp eo = (EqualityOp) bt;
                test = getInvertedEqualityTest(doc, eo);
            } else if (bt instanceof ComparisonOp) {
                ComparisonOp co = (ComparisonOp)bt;
                test = getInvertedComparisonTest(doc, co);
            } else if (! (bt instanceof Tree.OperatorExpression) 
                    || bt instanceof Tree.UnaryOperatorExpression) {
                term = removeEnclosingParenthesis(term);
            }
        } else {
               term = removeEnclosingParenthesis(term);
        }
        if (test == null) {
            if (term.startsWith("!")) {
                test = term.substring(1);
            }
            else {
                test = "!" + term;
            }
        }
        String elseIndent = utilJ2C().indents().getIndent(elseBlock, doc);
        String thenIndent = utilJ2C().indents().getIndent(ifBlock, doc);
//        String indent = getDefaultIndent();
        String delim = utilJ2C().indents().getDefaultLineDelimiter(doc);

        String elseStr = getTerm(doc, elseBlock);
//        elseStr = addEnclosingBraces(elseStr, 
//                baseIndent, indent, delim);
        test = removeEnclosingParenthesis(test);

        StringBuilder replace = new StringBuilder();
        replace.append("if (").append(test).append(")");
        if (isElseOnOwnLine(doc, ifCondition, ifBlock)) {
            replace.append(delim).append(thenIndent);
        } else {
            replace.append(" ");
        }
        replace.append("then ").append(elseStr);
        if (isElseOnOwnLine(doc, ifBlock, elseBlock)) {
            replace.append(delim).append(elseIndent);
        } else {
            replace.append(" ");
        }
        replace.append("else ")
            .append(getTerm(doc, ifBlock));

        TextChange change = 
                new TextFileChange("Invert If Then Else", 
                        file);
        change.setEdit(new ReplaceEdit(
                ifExpr.getStartIndex(), 
                ifExpr.getDistance(), 
                replace.toString()));
        proposals.add(new InvertIfElseProposal(
                ifExpr.getStartIndex(), 
                "Invert 'if' 'then' 'else' expression",
                change));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

    static void addInvertIfElseStatementProposal(IDocument doc,
                Collection<ICompletionProposal> proposals, 
                IFile file, final Statement statement, 
                Tree.CompilationUnit cu) {
        try {
            IfStatement ifStmt;
            if (statement instanceof IfStatement) {
                ifStmt = (IfStatement) statement;
            }
            else {
                class FindIf extends Visitor {
                    IfStatement result;
                    @Override
                    public void visit(IfStatement that) {
                        super.visit(that);
                        if (that.getIfClause()!=null &&
                            that.getElseClause()!=null &&
                            that.getStartIndex()<=statement.getStartIndex() &&
                            that.getEndIndex()>=statement.getEndIndex()) {
                            result = that;
                        }
                    }
                }
                FindIf fi = new FindIf();
                fi.visit(cu);
                if (fi.result==null) {
                    return;
                }
                else {
                    ifStmt = fi.result;
                }
            }
            
            IfClause ifClause = ifStmt.getIfClause();
            Block ifBlock = ifClause.getBlock();
            Block elseBlock = 
                    ifStmt.getElseClause()
                        .getBlock();
            List<Condition> conditions = 
                    ifClause.getConditionList()
                        .getConditions();
            if (conditions.size()!=1) {
                return;
            }
            Condition ifCondition = conditions.get(0);
            
            String test = null;
            String term = getTerm(doc, ifCondition);
            if (term.equals("(true)")) {
                test = "false";
            } else if (term.equals("(false)")) {
                test = "true";
            } else if (ifCondition instanceof BooleanCondition) {
                BooleanCondition boolCond = 
                        (BooleanCondition) ifCondition;
                Term bt = boolCond.getExpression().getTerm();
                if (bt instanceof NotOp) {
                    NotOp no = (NotOp) bt;
                    String t = getTerm(doc, no.getTerm());
                    test = removeEnclosingParenthesis(t);
                } else if (bt instanceof EqualityOp) {
                    EqualityOp eo = (EqualityOp) bt;
                    test = getInvertedEqualityTest(doc, eo);
                } else if (bt instanceof ComparisonOp) {
                    ComparisonOp co = (ComparisonOp)bt;
                    test = getInvertedComparisonTest(doc, co);
                } else if (! (bt instanceof Tree.OperatorExpression) 
                        || bt instanceof Tree.UnaryOperatorExpression) {
                    term = removeEnclosingParenthesis(term);
                }
            } else {
                   term = removeEnclosingParenthesis(term);
            }
            if (test == null) {
                if (term.startsWith("!")) {
                    test = term.substring(1);
                }
                else {
                    test = "!" + term;
                }
            }
            String baseIndent = utilJ2C().indents().getIndent(ifStmt, doc);
            String indent = utilJ2C().indents().getDefaultIndent();
            String delim = utilJ2C().indents().getDefaultLineDelimiter(doc);

            String elseStr = getTerm(doc, elseBlock);
            elseStr = 
                    addEnclosingBraces(elseStr, 
                            baseIndent, indent, delim);
            test = removeEnclosingParenthesis(test);

            StringBuilder replace = new StringBuilder();
            replace.append("if (").append(test).append(") ")
                    .append(elseStr);
            if (isElseOnOwnLine(doc, ifBlock, elseBlock)) {
                replace.append(delim).append(baseIndent);
            } else {
                replace.append(" ");
            }
            replace.append("else ")
                .append(getTerm(doc, ifBlock));

            TextChange change = 
                    new TextFileChange("Invert If Else", 
                            file);
            change.setEdit(new ReplaceEdit(
                    ifStmt.getStartIndex(), 
                    ifStmt.getDistance(), 
                    replace.toString()));
            proposals.add(new InvertIfElseProposal(
                    ifStmt.getStartIndex(), 
                    "Invert 'if' 'else' statement",
                    change));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static String getInvertedEqualityTest(IDocument doc, 
                EqualityOp equalityOp)
            throws BadLocationException {
        String op = 
                equalityOp instanceof EqualOp ? 
                        " != " : " == ";
        return getTerm(doc, 
                equalityOp.getLeftTerm()) + op + 
                getTerm(doc, equalityOp.getRightTerm());
    }

    private static String getInvertedComparisonTest(IDocument doc, 
            ComparisonOp compOp)
                    throws BadLocationException {
        String op;
        if (compOp instanceof LargerOp) {
            op = " <= ";
        } else if (compOp instanceof LargeAsOp) {
            op = " < ";
        } else if (compOp instanceof SmallerOp) {
            op = " >= ";
        } else if (compOp instanceof SmallAsOp) {
            op = " > ";
        } else {
            throw new RuntimeException("Unknown Comparision op " + 
                    compOp);
        }
        return getTerm(doc, 
                compOp.getLeftTerm()) + op + 
                getTerm(doc, compOp.getRightTerm());
    }


    private static boolean isElseOnOwnLine(IDocument doc, 
            Node ifBlock, Node elseBlock) 
                    throws BadLocationException {
        return doc.getLineOfOffset(ifBlock.getStopIndex()) != 
                doc.getLineOfOffset(elseBlock.getStartIndex());
    }

    private static String addEnclosingBraces(String s, 
            String baseIndent, String indent, String delim) {
        if (s.charAt(0) != '{') {
            return "{" + delim + baseIndent + 
                    indent + indent(s, indent, delim) + 
                    delim + baseIndent + "}";
        }
        return s;
    }
    
    private static String indent(String s, String indentation,
            String delim) {
        return s.replaceAll(delim+"(\\s*)", 
                delim+"$1" + indentation);
    }

    private static String removeEnclosingParenthesis(String s) {
        if (s.charAt(0) == '(') {
            int endIndex = 0;
            int startIndex = 0;
            //Make sure we are not in this case ((a) == (b))
            while ((endIndex = s.indexOf(')', endIndex + 1)) > 0) {
                if (endIndex == s.length() -1 ) {
                    return s.substring(1, s.length() - 1);
                }
                if ((startIndex = s.indexOf('(', startIndex + 1)) > endIndex) {
                    return s;
                }
            }
        }
        return s;
    }
    
    private static String getTerm(IDocument doc, Node node) 
            throws BadLocationException {
        return doc.get(node.getStartIndex(), node.getDistance());
    }
}